﻿// 
//    Copyright 2010 The JaernCloud Project
// 
//    Licensed under the Apache License, Version 2.0 (the "License");
//    you may not use this file except in compliance with the License.
//    You may obtain a copy of the License at
// 
//        http://www.apache.org/licenses/LICENSE-2.0
// 
//    Unless required by applicable law or agreed to in writing, software
//    distributed under the License is distributed on an "AS IS" BASIS,
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//    See the License for the specific language governing permissions and
//    limitations under the License.
// 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using JaernCloud.Language.Compiler.Types;
using JaernCloud.Language.Compiler.Types.Builtin.Model;
using JaernCloud.Language.Runtime.Model;
using JaernCloud.Language.Runtime.View;
using JaernCloud.Language.Runtime.Page;

namespace JaernCloud.Language.Runtime {

    /// <summary>
    /// Generates INodeObject runtime instances based on declared INodeType type declarations.
    /// </summary>
    public class NodeObjectGenerator : INodeObjectGenerator {

        private INodeObjectScopeProvider _objectScopeProvider;

        public NodeObjectGenerator(INodeObjectScopeProvider objectScopeProvider) {
            this._objectScopeProvider = objectScopeProvider;
        }

        public INodeObjectScopeProvider NodeObjectScopeProvider {
            get { return _objectScopeProvider; }
        }

        static NodeObject<INodeType> _default = new NodeObject<INodeType>();
        static IntegerNodeObject _integer = new IntegerNodeObject();
        static StringNodeObject _string = new StringNodeObject();
        static UserNodeObject _user = new UserNodeObject();

        static IdentifierNodeObject _identifier = new IdentifierNodeObject();
        static ExpressionNodeObject _expression = new ExpressionNodeObject();


        static ModelNodeObject _model = new ModelNodeObject();
        static ViewNodeObject _view = new ViewNodeObject();
        static PageNodeObject _page = new PageNodeObject();

        static RenderNodeObject _render = new RenderNodeObject();

        static QueryNodeObject _query = new QueryNodeObject();

        bool createFromScopedParent = true;

        public INodeObject<INodeType> GetNodeQueryResultObjectInstanceFor(INodeType nodeType) {
            return GetNodeObjectInstanceFor(nodeType, true);
        }

        public INodeObject<INodeType> GetNodeObjectInstanceFor(INodeType nodeType) {
            return GetNodeObjectInstanceFor(nodeType, false);
        }

        private INodeObject<INodeType> GetNodeObjectInstanceFor(INodeType nodeType, bool asQueryResult) {

            if (nodeType.Parent is IScopedNodeType && nodeType.Parent is IModelNodeType<INodeType> && createFromScopedParent) {
                // the parent is scoped model, create it first and see if we can return the child it has created
                createFromScopedParent = false;
                try {
                    INodeObject<INodeType> scopedParent = GetNodeObjectInstanceFor(nodeType.Parent);
                    foreach (INodeObject<INodeType> sibling in scopedParent.Children) {
                        if (sibling.Type == nodeType) {
                            // found the node object corresponding to the requested nodeType as a child of a scoped parent
                            // return that same instance instead of creating a new one
                            return sibling;
                        }
                    }
                } finally {
                    createFromScopedParent = true;
                }
            }

            if (!asQueryResult) {
                INodeType queryNodeType = nodeType.InheritsFrom.SingleOrDefault(t => t is IQueryNodeType);
                if (queryNodeType != null) {
                    return _query.FromType(queryNodeType, this);
                }
            }

            // expression
            if (nodeType is IExpressionNodeType) {
                return _expression.FromType(nodeType, this);
            }

            // identifier
            if (nodeType is IIdentifierNodeType) {
                return _identifier.FromType(nodeType, this);
            }

            // constants
            if (nodeType is IConstantNodeType) {

            }

            // models
            if (nodeType is IModelNodeType<INodeType>) {
                
                INodeObject<INodeType> scopedObject = NodeObjectScopeProvider.Get((IScopedNodeType)nodeType, this);
                if (scopedObject != null) {
                    return scopedObject;
                }

                if (nodeType is StringNodeType) {
                    scopedObject = _string.FromType(nodeType, this);
                } else if (nodeType is IntegerNodeType) {
                    scopedObject = _integer.FromType(nodeType, this);
                } else if (nodeType is UserNodeType) {
                    scopedObject = _user.FromType(nodeType, this);
                } else if (nodeType is BooleanNodeType) {
                    throw new NotSupportedException("TODO '" + nodeType + "' is not supported");
                } else if (nodeType is LongNodeType) {
                    throw new NotSupportedException("TODO '" + nodeType + "' is not supported");
                } else if (nodeType is FloatNodeType) {
                    throw new NotSupportedException("TODO '" + nodeType + "' is not supported");
                } else if (nodeType is DoubleNodeType) {
                    throw new NotSupportedException("TODO '" + nodeType + "' is not supported");
                } else {
                    // model entity object                               
                    scopedObject = _model.FromType(nodeType, this);
                    if (!(scopedObject is IScopedNodeObject<IScopedNodeType>)) {
                        throw new NotSupportedException("TODO: Other built int model types (Email, Password, Regex, Url, JSON, XML, User");
                    }
                }
                                
                if (scopedObject is IScopedNodeObject<IScopedNodeType>) {
                    // add to scope and return
                    return NodeObjectScopeProvider.Put((IScopedNodeObject<IScopedNodeType>)scopedObject);
                }

                return scopedObject;

            }

            // view
            if (nodeType is IViewNodeType) {
                return _view.FromType(nodeType, this);
            }

            // page
            if (nodeType is IPageNodeType) {
                return _page.FromType(nodeType, this);
            }

            // render node
            if (nodeType is IRenderNodeType) {
                return _render.FromType(nodeType, this);
            }


            return _default.FromType(nodeType, this);

            // not supported
            throw new NotSupportedException("'" + nodeType + "' is not supported");

        }

    }

    public static class NodeObjectGeneratorExtensions {

        /// <summary>
        /// 
        /// </summary>
        public static INodeObject<INodeType> Cast<T>(this INodeObject<T> nodeObject) where T: INodeType {
            return (INodeObject<INodeType>)nodeObject;
        }
    }
}
